package com.google.gwt.gfx.client.layers;

import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.gfx.client.overlays.GFXImage;
import com.google.gwt.gfx.client.services.TileService;
import com.google.gwt.gfx.client.shared.GlobalPoint;

/**
 * A type of {@link Layer} that represents raster data as grid-aligned tiles
 * covering the window size specified by {@link #viewport()}.Tiles in this layer
 * can be cached for more immediate response.
 * 
 * @see TileService
 */
public class TileLayer<S extends TileService<?>> extends Layer<S> {

	private int prevLevel = -1;

	private int prevMaxCol = -1;

	private int prevMaxRow = -1;

	private int prevMinCol = -1;

	private int prevMinRow = -1;

	public TileLayer() {
		super();

		addStyleName("gfx-TileLayer");
	}

	public void addTile(int col, int row, int level, int offsetLeft,
			int offsetTop) {
		GFXImage tile = getTile(col, row, level);
		Style tileStyle = tile.getElement().getStyle();
		tileStyle.setWidth(service().getTileWidth(), Unit.PX);
		tileStyle.setHeight(service().getTileHeight(), Unit.PX);
		tileStyle.setLeft(offsetLeft + col * service().getTileWidth(), Unit.PX);
		tileStyle.setTop(offsetTop + row * service().getTileHeight(), Unit.PX);
		add(tile);
	}

	/**
	 * Gets a tile image over network of specific column, row and level.
	 * 
	 * @param col
	 *            column of a tile in the grid
	 * @param row
	 *            row of a tile in the grid
	 * @param level
	 *            level of a tile grid
	 * @return tile of specific column , row and level
	 */
	public GFXImage getTile(int col, int row, int level) {
		return GFXImage.get(service().getTileID(col, row, level), service()
				.getTileUrl(col, row, level));
	}

	@Override
	public void refresh() {
		int mapWidth = service().viewport().getWidth();
		int mapHeight = service().viewport().getHeight();
		int tileWidth = service().getTileWidth();
		int tileHeight = service().getTileHeight();

		GlobalPoint global = service().viewport().getPosition();
		int minLeft = global.getLeft() - mapWidth / 2;
		int minTop = global.getTop() - mapHeight / 2;
		int maxLeft = global.getLeft() + mapWidth / 2;
		int maxTop = global.getTop() + mapHeight / 2;

		int minCol = minLeft / tileWidth - 1;
		int maxCol = maxLeft / tileWidth + 1;
		int minRow = minTop / tileHeight - 1;
		int maxRow = maxTop / tileHeight + 1;
		int level = service().getLevel();

		int offsetLeft = service().getOffsetLeft() - minLeft
				- getElement().getOffsetLeft();
		int offsetTop = service().getOffsetTop() - minTop
				- getElement().getOffsetTop();

		if (prevLevel != level) {
			while (getWidgetCount() > 0) {
				getWidget(0).removeFromParent();
			}
			for (int col = minCol; col < maxCol; col++) {
				for (int row = minRow; row < maxRow; row++) {
					GFXImage tile = getTile(col, row, level);
					Style tileStyle = tile.getElement().getStyle();
					tileStyle.setWidth(tileWidth, Unit.PX);
					tileStyle.setHeight(tileHeight, Unit.PX);
					tileStyle.setLeft(offsetLeft + col * tileWidth, Unit.PX);
					tileStyle.setTop(offsetTop + row * tileHeight, Unit.PX);
					add(tile);
				}
			}
		} else {
			// move the intersection to new position, in fact this code is not
			// necessary if this function is called by {@link
			// MapViewer#moveBackAndRefresh}.
			for (int col = Math.max(prevMinCol, minCol); col < Math.min(
					prevMaxCol, maxCol); col++) {
				for (int row = Math.max(prevMinRow, minRow); row < Math.min(
						prevMaxRow, maxRow); row++) {
					GFXImage tile = getTile(col, row, level);
					Style tileStyle = tile.getElement().getStyle();
					tileStyle.setLeft(offsetLeft + col * tileWidth, Unit.PX);
					tileStyle.setTop(offsetTop + row * tileHeight, Unit.PX);
				}
			}

			// Remove unnecessary tiles.
			if (prevMinCol < minCol) {
				for (int col = prevMinCol; col < minCol; col++) {
					for (int row = prevMinRow; row < prevMaxRow; row++) {
						removeTile(col, row, level);
					}
				}
			}
			if (prevMinRow < minRow) {
				for (int col = minCol; col < maxCol; col++) {
					for (int row = prevMinRow; row < minRow; row++) {
						removeTile(col, row, level);
					}
				}
			}
			if (maxCol < prevMaxCol) {
				for (int col = maxCol; col < prevMaxCol; col++) {
					for (int row = prevMinRow; row < prevMaxRow; row++) {
						removeTile(col, row, level);
					}
				}
			}
			if (maxRow < prevMaxRow) {
				for (int col = minCol; col < maxCol; col++) {
					for (int row = maxRow; row < prevMaxRow; row++) {
						removeTile(col, row, level);
					}
				}
			}

			// Add necessary tiles.
			if (minCol < prevMinCol) {
				for (int col = minCol; col < prevMinCol; col++) {
					for (int row = minRow; row < maxRow; row++) {
						addTile(col, row, level, offsetLeft, offsetTop);
					}
				}
			}
			if (minRow < prevMinRow) {
				for (int col = prevMinCol; col < prevMaxCol; col++) {
					for (int row = minRow; row < prevMinRow; row++) {
						addTile(col, row, level, offsetLeft, offsetTop);
					}
				}
			}
			if (prevMaxCol < maxCol) {
				for (int col = prevMaxCol; col < maxCol; col++) {
					for (int row = minRow; row < maxRow; row++) {
						addTile(col, row, level, offsetLeft, offsetTop);
					}
				}
			}
			if (prevMaxRow < maxRow) {
				for (int col = prevMinCol; col < prevMaxCol; col++) {
					for (int row = prevMaxRow; row < maxRow; row++) {
						addTile(col, row, level, offsetLeft, offsetTop);
					}
				}
			}
		}

		prevMinCol = minCol;
		prevMinRow = minRow;
		prevMaxCol = maxCol;
		prevMaxRow = maxRow;
		prevLevel = level;
	}

	public void removeTile(int col, int row, int level) {
		getTile(col, row, level).removeFromParent();
	}

}
